/**
 * @param {Object}  obj
 * @param {Function} Fn
 * @param {*}args
 */
function call(obj, Fn, ...args) {
	if ([null, undefined].includes(obj)) {
		obj = globalThis;
	}
	obj.temp = Fn;
	obj.temp(...args);
	delete obj.temp;
}

/**
 *
 * @param {Object} obj
 * @param {Function} Fn
 * @param {[]} args
 */
function apply(obj, Fn, args) {
	if ([null, undefined].includes(obj)) {
		obj = globalThis;
	}
	obj.temp = Fn;
	obj.temp(args);
	delete obj.temp;
}

/**
 *
 * @param {Object} obj
 * @param {Function} Fn
 * @param {*} args
 * @return {(function(...[*]): void)|*}
 */
function bind(obj, Fn, ...args) {
	return function (...args2) {
		if ([undefined, null].includes(obj)) {
			obj = globalThis;
		}
		obj.temp = Fn;
		obj.temp(...args, ...args2);
		delete obj.temp;
	}
}

/**
 *
 * @param {Function} Fn
 * @param {*} args
 * @return {*}
 */
function newInstance(Fn, ...args) {
	let obj = Object.create(Fn.prototype);
	let result = obj.apply(Fn, args);
	return result instanceof Object ? result : obj;
}

/**
 *
 * @param {Function} Fn
 * @param {Object} obj
 * @return {boolean}
 */
function instanceOf(Fn, obj) {
	let prototype = Fn.prototype,
		__proto__ = Object.getPrototypeOf(obj);
	while (__proto__) {
		if (prototype === __proto__) {
			return true;
		}
		__proto__ = Object.getPrototypeOf(__proto__);
	}
	return false;
}

/**
 *
 * @param {[Object]} objs
 * @return {{}}
 */
function mergeObj(...objs) {
	let result = {};
	objs.forEach(obj => {
		Object.keys(obj).forEach(key => {// key   a b c d b
			if (result.hasOwnProperty(key)) {
				result[key] = result[key].concat(obj[key])
			} else {
				result[key] = [obj[key]];
			}
		})
	})
	return result;
}

/**
 *
 * @param {Array | Object}target
 * @param {Map} map
 * @return {any}
 */
function deepClone(target, map = new Map()) {
	if (target !== null && typeof target === 'object') {
		// 防止循环引用
		let cache = map.get(target);
		if (cache) return cache;
		let result = Array.isArray(target) ? [] : {};
		map.set(target, result);
		for (let targetKey in target) {
			if (target.hasOwnProperty(targetKey)) {// 不能去拷贝原型属性
				result[targetKey] = deepClone(target[targetKey], map);
			}
		}
		return result;
	} else {
		return target;
	}
}

/**
 *
 * @param {[]} arr
 * @param {Function} callback
 * @return {*[]}
 */
function map(arr, callback) {
	let result = [];
	for (let i = 0; i < arr.length; i++) {
		result.push(callback(arr[i], i))
	}
	return result;
}

/**
 *
 * @param {[]} arr
 * @param {Function} callback
 * @param {*} init
 * @return {*}
 */
function reduce(arr, callback, init) {
	let result = init;
	for (let i = 0; i < arr.length; i++) {
		result = callback(arr[i], result);
	}
	return result;
}

/**
 *
 * @param {[]} arr
 * @param {Function} callback
 * @return {*[]}
 */
function filter(arr, callback) {
	let result = [];
	for (let i = 0; i < arr.length; i++) {
		if (callback(arr[i], i)) {
			result.push(arr[i])
		}
	}
	return result;
}

/**
 *
 * @param {[]} arr
 * @param {Function} callback
 * @return {*}
 */
function find(arr, callback) {
	for (let i = 0; i < arr.length; i++) {
		if (callback(arr[i], i)) return arr[i];
	}
}

/**
 *
 * @param {[]} arr
 * @param {Function} callback
 * @return {number}
 */
function findIndex(arr, callback) {
	for (let i = 0; i < arr.length; i++) {
		if (callback(arr[i], i)) return i;
	}
	return -1;
}

/**
 *
 * @param {[]} arr
 * @param {Function}  callback
 * @return {boolean}
 */
function every(arr, callback) {
	for (let i = 0; i < arr.length; i++) {
		if (!callback(arr[i], i)) return false;
	}
	return true;
}

/**
 *
 * @param {[]} arr
 * @param {Function} callback
 * @return {boolean}
 */
function some(arr, callback) {
	for (let i = 0; i < arr.length; i++) {
		if (callback(arr[i], i)) return true;
	}
	return false;
}

/**
 *
 * @param {[]} arr
 * @return {any[]}
 */
function unique(arr) {
	return [...new Set(arr)];
}

/**
 *
 * @param {[]} arr
 * @param {*} args
 * @return {*[]}
 */
function concat(arr, ...args) {
	let result = [...arr];
	args.forEach(item => {
		if (Array.isArray(item)) {
			result.push(...item);
		} else {
			result.push(item)
		}
	});
	return result;
}

/**
 *
 * @param {[]} arr
 * @param {Number} start
 * @param {Number} end
 * @return {*[]}
 */
function slice(arr,start=0,end=arr.length) {
	if (!arr.length) return [];
	if (end>start) return  [];
	let result=[];
	for (let i = 0; i < arr.length; i++) {
		if (start>=i && end <i){
			result.push(arr[i]);
		}
	}
	return  result;
}

/**
 *
 * @param arr
 * @return {*[]}
 */
function flat(arr) {
	let result=[...arr];
	while ((result.some(item=>Array.isArray(item)))){
		result=[].concat(...result)
	}
	return result;
}

/**
 *
 * @param arr
 * @param size
 * @return {*[]}
 */
function chunk(arr,size=1) {
	let result=[],
		temp=[];
	for (let i = 0; i < arr.length; i++) {
		if (temp.length===0){
			result.push(temp);
		}
		temp.push(arr[i]);
		if (temp.length===size){
			temp=[];
		}
	}
	return result;
}

/**
 *
 * @param {String | Element} el
 * @param {string} type
 * @param {Function} callback
 * @param  {string} selector
 */
function delegate(el,type,callback,selector) {
  if (typeof el ==='string'){
		el=document.querySelector(el);
	}
	if (!selector){
		el.addEventListener(type,callback);
	}else {
		el.addEventListener(type,function (e) {
			let target=e.target;
			if (target.matches(selector)){
				callback.call(target,e);
			}
		})
	}
}

/**
 *
 * @param {Function} callback
 * @param {Number} wait
 * @return {(function(*): void)|*}
 */
function throttle(callback,wait) {
	let start=0;
	return function (e) {
		let now=Date.now();
		if (now-start >=wait){
			callback.call(this,e);
			start=now;
		}
	}
}

/**
 *
 * @param {Function} callback
 * @param {Number}  wait
 * @return {(function(*): void)|*}
 */
function debounce(callback,wait) {
	let timer;
	return function (e) {
		if (timer) clearTimeout(timer);
		timer=setTimeout(()=>{
			callback.call(this,e);
			timer=null;
		},wait);
	}
}

const eventBus={
	callbacks:{},
	on(type,callback){ // on('select',function(){})
		if (this.callbacks.hasOwnProperty(type)){
			this.callbacks[type].push(callback);
		}else {
			this.callbacks[type]=[callback];
		}
	},
	emit(event,data){ // emit('select',{a:1,b:2})
		let callbacks=this.callbacks[event];
		if (callbacks && callbacks.length>0){
			callbacks.forEach(callback=>{
				callback(data);
			});
		}
	},
	off(...event){ // ('') ('select','select2',...)
		if (!event.length){
			this.callbacks={};
		}else {
			event.forEach(e=>{
				delete  this.callbacks[e];
			})
		}
	}
}

const pubSub={
	id:1,
	callbacks:{}, // {pay:{token_1:fn,token_2:fn,token_3:fn},pay2:{token_3:fn,token_4:fn,token_5:fn}}
	tokenPrefix:'token_',
	subscribe(channel,callback){ // subscribe('pay',function(){})
		let token=this.tokenPrefix+this.id++;
		if (this.callbacks.hasOwnProperty(channel)){
			this.callbacks[channel][token]=callback;
		}else {
			this.callbacks[channel]={
				[token]:callback
			}
		}
		return token;
	},
	publish(channel,data){ // publish('pay',{a:1,b:2,c:3})
		if (this.callbacks.hasOwnProperty(channel)){
			Object.values(this.callbacks[channel]).forEach(callback=>{
				callback(data);
			})
		}
	},
	unsubscribe(token){// unsubscribe(token)  unsubscribe('pay')
     if (!token){
			 this.callbacks={};
		 }
		 else if (token.startsWith(this.tokenPrefix)){ // token_1
			 let callbackObj = Object.values(this.callbacks).find(obj => obj.hasOwnProperty(token));
			 if (callbackObj) {
				 delete callbackObj[token];
			 }
		 }else {  // pay
			 delete  this.callbacks[token];
		 }
	}
}
